﻿/*
Copyright (c) 2012 Eric Lasota

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using System;
using System.Collections.Generic;

using System.Text;

namespace MPEGnet
{
    internal struct IntRow
    {
        public int v0, v1, v2, v3, v4, v5, v6, v7;

        public IntRow(int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7)
        {
            this.v0 = v0;
            this.v1 = v1;
            this.v2 = v2;
            this.v3 = v3;
            this.v4 = v4;
            this.v5 = v5;
            this.v6 = v6;
            this.v7 = v7;
        }

        public IntRow(DCTROW row)
        {
            this.v0 = row.v0;
            this.v1 = row.v1;
            this.v2 = row.v2;
            this.v3 = row.v3;
            this.v4 = row.v4;
            this.v5 = row.v5;
            this.v6 = row.v6;
            this.v7 = row.v7;
        }

        public IntRow(int singleVal)
        {
            v0 =
            v1 =
            v2 =
            v3 =
            v4 =
            v5 =
            v6 =
            v7 = singleVal;
        }

        public IntRow(byte[] numbers, int startIndex)
        {
            v0 = numbers[startIndex + 0];
            v1 = numbers[startIndex + 1];
            v2 = numbers[startIndex + 2];
            v3 = numbers[startIndex + 3];
            v4 = numbers[startIndex + 4];
            v5 = numbers[startIndex + 5];
            v6 = numbers[startIndex + 6];
            v7 = numbers[startIndex + 7];

            /*
            long l = System.BitConverter.ToInt64(numbers, startIndex);
            if (System.BitConverter.IsLittleEndian)
            {
                v0 = (int)((l >> 0) & 0xff);
                v1 = (int)((l >> 8) & 0xff);
                v2 = (int)((l >> 16) & 0xff);
                v3 = (int)((l >> 24) & 0xff);
                v4 = (int)((l >> 32) & 0xff);
                v5 = (int)((l >> 40) & 0xff);
                v6 = (int)((l >> 48) & 0xff);
                v7 = (int)((l >> 56) & 0xff);
            }
            else
            {
                v7 = (int)((l >> 0) & 0xff);
                v6 = (int)((l >> 8) & 0xff);
                v5 = (int)((l >> 16) & 0xff);
                v4 = (int)((l >> 24) & 0xff);
                v3 = (int)((l >> 32) & 0xff);
                v2 = (int)((l >> 40) & 0xff);
                v1 = (int)((l >> 48) & 0xff);
                v0 = (int)((l >> 56) & 0xff);
            }
             */
        }

        public void ClampAndWrite(byte[] output, int startIndex)
        {
            output[startIndex + 0] = MPEGnet.video.BClamp(v0);
            output[startIndex + 1] = MPEGnet.video.BClamp(v1);
            output[startIndex + 2] = MPEGnet.video.BClamp(v2);
            output[startIndex + 3] = MPEGnet.video.BClamp(v3);
            output[startIndex + 4] = MPEGnet.video.BClamp(v4);
            output[startIndex + 5] = MPEGnet.video.BClamp(v5);
            output[startIndex + 6] = MPEGnet.video.BClamp(v6);
            output[startIndex + 7] = MPEGnet.video.BClamp(v7);
        }

        public void SimpleWrite(byte[] output, int startIndex)
        {
            output[startIndex + 0] = (byte)v0;
            output[startIndex + 1] = (byte)v1;
            output[startIndex + 2] = (byte)v2;
            output[startIndex + 3] = (byte)v3;
            output[startIndex + 4] = (byte)v4;
            output[startIndex + 5] = (byte)v5;
            output[startIndex + 6] = (byte)v6;
            output[startIndex + 7] = (byte)v7;
        }

        public IntRow Add(IntRow rs)
        {
            return new IntRow(v0 + rs.v0,
                v1 + rs.v1,
                v2 + rs.v2,
                v3 + rs.v3,
                v4 + rs.v4,
                v5 + rs.v5,
                v6 + rs.v6,
                v7 + rs.v7);
        }


        public IntRow RightShift(int rs)
        {
            return new IntRow(v0 >> rs,
                v1 >> rs,
                v2 >> rs,
                v3 >> rs,
                v4 >> rs,
                v5 >> rs,
                v6 >> rs,
                v7 >> rs);
        }

        internal void AverageWrite(ref IntRow rs, byte[] output, int startIndex)
        {
            output[startIndex + 0] = (byte)((v0 + rs.v0 + 1) >> 1);
            output[startIndex + 1] = (byte)((v1 + rs.v1 + 1) >> 1);
            output[startIndex + 2] = (byte)((v2 + rs.v2 + 1) >> 1);
            output[startIndex + 3] = (byte)((v3 + rs.v3 + 1) >> 1);
            output[startIndex + 4] = (byte)((v4 + rs.v4 + 1) >> 1);
            output[startIndex + 5] = (byte)((v5 + rs.v5 + 1) >> 1);
            output[startIndex + 6] = (byte)((v6 + rs.v6 + 1) >> 1);
            output[startIndex + 7] = (byte)((v7 + rs.v7 + 1) >> 1);
        }
    }

    internal struct IntBlock
    {
        public IntRow r0, r1, r2, r3, r4, r5, r6, r7;

        public IntBlock(DCTBLOCK blk)
        {
            this.r0 = new IntRow(blk.r0);
            this.r1 = new IntRow(blk.r1);
            this.r2 = new IntRow(blk.r2);
            this.r3 = new IntRow(blk.r3);
            this.r4 = new IntRow(blk.r4);
            this.r5 = new IntRow(blk.r5);
            this.r6 = new IntRow(blk.r6);
            this.r7 = new IntRow(blk.r7);
        }

        public IntBlock(IntRow r0, IntRow r1, IntRow r2, IntRow r3, IntRow r4, IntRow r5, IntRow r6, IntRow r7)
        {
            this.r0 = r0;
            this.r1 = r1;
            this.r2 = r2;
            this.r3 = r3;
            this.r4 = r4;
            this.r5 = r5;
            this.r6 = r6;
            this.r7 = r7;
        }

        public IntBlock(int singleVal)
        {
            r0 =
            r1 =
            r2 =
            r3 =
            r4 =
            r5 =
            r6 =
            r7 = new IntRow(singleVal);
        }

        public IntBlock(IntRow singleVal)
        {
            r0 =
            r1 =
            r2 =
            r3 =
            r4 =
            r5 =
            r6 =
            r7 = singleVal;
        }

        public IntBlock(byte[] numbers, int startIndex, int stride)
        {
            r0 = new IntRow(numbers, startIndex); startIndex += stride;
            r1 = new IntRow(numbers, startIndex); startIndex += stride;
            r2 = new IntRow(numbers, startIndex); startIndex += stride;
            r3 = new IntRow(numbers, startIndex); startIndex += stride;
            r4 = new IntRow(numbers, startIndex); startIndex += stride;
            r5 = new IntRow(numbers, startIndex); startIndex += stride;
            r6 = new IntRow(numbers, startIndex); startIndex += stride;
            r7 = new IntRow(numbers, startIndex);
        }

        public void ClampAndWrite(byte[] output, int startIndex, int stride)
        {
            r0.ClampAndWrite(output, startIndex + stride * 0);
            r1.ClampAndWrite(output, startIndex + stride * 1);
            r2.ClampAndWrite(output, startIndex + stride * 2);
            r3.ClampAndWrite(output, startIndex + stride * 3);
            r4.ClampAndWrite(output, startIndex + stride * 4);
            r5.ClampAndWrite(output, startIndex + stride * 5);
            r6.ClampAndWrite(output, startIndex + stride * 6);
            r7.ClampAndWrite(output, startIndex + stride * 7);
        }

        public void SimpleWrite(byte[] output, int startIndex, int stride)
        {
            r0.SimpleWrite(output, startIndex + stride * 0);
            r1.SimpleWrite(output, startIndex + stride * 1);
            r2.SimpleWrite(output, startIndex + stride * 2);
            r3.SimpleWrite(output, startIndex + stride * 3);
            r4.SimpleWrite(output, startIndex + stride * 4);
            r5.SimpleWrite(output, startIndex + stride * 5);
            r6.SimpleWrite(output, startIndex + stride * 6);
            r7.SimpleWrite(output, startIndex + stride * 7);
        }

        public IntBlock Add(IntBlock rs)
        {
            return new IntBlock(
                r0.Add(rs.r0),
                r1.Add(rs.r1),
                r2.Add(rs.r2),
                r3.Add(rs.r3),
                r4.Add(rs.r4),
                r5.Add(rs.r5),
                r6.Add(rs.r6),
                r7.Add(rs.r7)
                );
        }

        public IntBlock RightShift(int rs)
        {
            return new IntBlock(
                    r0.RightShift(rs),
                    r1.RightShift(rs),
                    r2.RightShift(rs),
                    r3.RightShift(rs),
                    r4.RightShift(rs),
                    r5.RightShift(rs),
                    r6.RightShift(rs),
                    r7.RightShift(rs)
                );
        }

        internal void AverageWrite(ref IntBlock rs, byte[] output, int startIndex, int stride)
        {
            r0.AverageWrite(ref rs.r0, output, startIndex + stride * 0);
            r1.AverageWrite(ref rs.r1, output, startIndex + stride * 1);
            r2.AverageWrite(ref rs.r2, output, startIndex + stride * 2);
            r3.AverageWrite(ref rs.r3, output, startIndex + stride * 3);
            r4.AverageWrite(ref rs.r4, output, startIndex + stride * 4);
            r5.AverageWrite(ref rs.r5, output, startIndex + stride * 5);
            r6.AverageWrite(ref rs.r6, output, startIndex + stride * 6);
            r7.AverageWrite(ref rs.r7, output, startIndex + stride * 7);
        }
    }


    // ******************************************************************************
    // 16x16

    internal struct IntRow16
    {
        public int v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15;

        public IntRow16(int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, int v14, int v15)
        {
            this.v0 = v0;
            this.v1 = v1;
            this.v2 = v2;
            this.v3 = v3;
            this.v4 = v4;
            this.v5 = v5;
            this.v6 = v6;
            this.v7 = v7;
            this.v8 = v8;
            this.v9 = v9;
            this.v10 = v10;
            this.v11 = v11;
            this.v12 = v12;
            this.v13 = v13;
            this.v14 = v14;
            this.v15 = v15;
        }

        public IntRow16(int singleVal)
        {
            v0 =
            v1 =
            v2 =
            v3 =
            v4 =
            v5 =
            v6 =
            v7 =
            v8 =
            v9 =
            v10 =
            v11 =
            v12 =
            v13 =
            v14 =
            v15 = singleVal;
        }

        public IntRow16(byte[] numbers, int startIndex)
        {
            v0 = numbers[startIndex + 0];
            v1 = numbers[startIndex + 1];
            v2 = numbers[startIndex + 2];
            v3 = numbers[startIndex + 3];
            v4 = numbers[startIndex + 4];
            v5 = numbers[startIndex + 5];
            v6 = numbers[startIndex + 6];
            v7 = numbers[startIndex + 7];
            v8 = numbers[startIndex + 8];
            v9 = numbers[startIndex + 9];
            v10 = numbers[startIndex + 10];
            v11 = numbers[startIndex + 11];
            v12 = numbers[startIndex + 12];
            v13 = numbers[startIndex + 13];
            v14 = numbers[startIndex + 14];
            v15 = numbers[startIndex + 15];

            /*
            long l0 = System.BitConverter.ToInt64(numbers, startIndex);
            long l1 = System.BitConverter.ToInt64(numbers, startIndex + 8);

            if (System.BitConverter.IsLittleEndian)
            {
                v0 = (int)((l0 >> 0) & 0xff);
                v1 = (int)((l0 >> 8) & 0xff);
                v2 = (int)((l0 >> 16) & 0xff);
                v3 = (int)((l0 >> 24) & 0xff);
                v4 = (int)((l0 >> 32) & 0xff);
                v5 = (int)((l0 >> 40) & 0xff);
                v6 = (int)((l0 >> 48) & 0xff);
                v7 = (int)((l0 >> 56) & 0xff);
                v8 = (int)((l1 >> 0) & 0xff);
                v9 = (int)((l1 >> 8) & 0xff);
                v10 = (int)((l1 >> 16) & 0xff);
                v11 = (int)((l1 >> 24) & 0xff);
                v12 = (int)((l1 >> 32) & 0xff);
                v13 = (int)((l1 >> 40) & 0xff);
                v14 = (int)((l1 >> 48) & 0xff);
                v15 = (int)((l1 >> 56) & 0xff);
            }
            else
            {
                v7 = (int)((l0 >> 0) & 0xff);
                v6 = (int)((l0 >> 8) & 0xff);
                v5 = (int)((l0 >> 16) & 0xff);
                v4 = (int)((l0 >> 24) & 0xff);
                v3 = (int)((l0 >> 32) & 0xff);
                v2 = (int)((l0 >> 40) & 0xff);
                v1 = (int)((l0 >> 48) & 0xff);
                v0 = (int)((l0 >> 56) & 0xff);

                v15 = (int)((l1 >> 0) & 0xff);
                v14 = (int)((l1 >> 8) & 0xff);
                v13 = (int)((l1 >> 16) & 0xff);
                v12 = (int)((l1 >> 24) & 0xff);
                v11 = (int)((l1 >> 32) & 0xff);
                v10 = (int)((l1 >> 40) & 0xff);
                v9 = (int)((l1 >> 48) & 0xff);
                v8 = (int)((l1 >> 56) & 0xff);
            }
             * */
        }

        public void ClampAndWrite(byte[] output, int startIndex)
        {
            output[startIndex + 0] = MPEGnet.video.BClamp(v0);
            output[startIndex + 1] = MPEGnet.video.BClamp(v1);
            output[startIndex + 2] = MPEGnet.video.BClamp(v2);
            output[startIndex + 3] = MPEGnet.video.BClamp(v3);
            output[startIndex + 4] = MPEGnet.video.BClamp(v4);
            output[startIndex + 5] = MPEGnet.video.BClamp(v5);
            output[startIndex + 6] = MPEGnet.video.BClamp(v6);
            output[startIndex + 7] = MPEGnet.video.BClamp(v7);
            output[startIndex + 8] = MPEGnet.video.BClamp(v8);
            output[startIndex + 9] = MPEGnet.video.BClamp(v9);
            output[startIndex + 10] = MPEGnet.video.BClamp(v10);
            output[startIndex + 11] = MPEGnet.video.BClamp(v11);
            output[startIndex + 12] = MPEGnet.video.BClamp(v12);
            output[startIndex + 13] = MPEGnet.video.BClamp(v13);
            output[startIndex + 14] = MPEGnet.video.BClamp(v14);
            output[startIndex + 15] = MPEGnet.video.BClamp(v15);
        }

        public void SimpleWrite(byte[] output, int startIndex)
        {
            output[startIndex + 0] = (byte)v0;
            output[startIndex + 1] = (byte)v1;
            output[startIndex + 2] = (byte)v2;
            output[startIndex + 3] = (byte)v3;
            output[startIndex + 4] = (byte)v4;
            output[startIndex + 5] = (byte)v5;
            output[startIndex + 6] = (byte)v6;
            output[startIndex + 7] = (byte)v7;
            output[startIndex + 8] = (byte)v8;
            output[startIndex + 9] = (byte)v9;
            output[startIndex + 10] = (byte)v10;
            output[startIndex + 11] = (byte)v11;
            output[startIndex + 12] = (byte)v12;
            output[startIndex + 13] = (byte)v13;
            output[startIndex + 14] = (byte)v14;
            output[startIndex + 15] = (byte)v15;
        }

        public IntRow16 Add(IntRow16 rs)
        {
            return new IntRow16(v0 + rs.v0,
                v1 + rs.v1,
                v2 + rs.v2,
                v3 + rs.v3,
                v4 + rs.v4,
                v5 + rs.v5,
                v6 + rs.v6,
                v7 + rs.v7,
                v8 + rs.v8,
                v9 + rs.v9,
                v10 + rs.v10,
                v11 + rs.v11,
                v12 + rs.v12,
                v13 + rs.v13,
                v14 + rs.v14,
                v15 + rs.v15
                );
        }


        public IntRow16 RightShift(int rs)
        {
            return new IntRow16(v0 >> rs,
                v1 >> rs,
                v2 >> rs,
                v3 >> rs,
                v4 >> rs,
                v5 >> rs,
                v6 >> rs,
                v7 >> rs,
                v8 >> rs,
                v9 >> rs,
                v10 >> rs,
                v11 >> rs,
                v12 >> rs,
                v13 >> rs,
                v14 >> rs,
                v15 >> rs);
        }

        internal void AverageWrite(ref IntRow16 rs, byte[] output, int startIndex)
        {
            output[startIndex + 0] = (byte)((v0 + rs.v0 + 1) >> 1);
            output[startIndex + 1] = (byte)((v1 + rs.v1 + 1) >> 1);
            output[startIndex + 2] = (byte)((v2 + rs.v2 + 1) >> 1);
            output[startIndex + 3] = (byte)((v3 + rs.v3 + 1) >> 1);
            output[startIndex + 4] = (byte)((v4 + rs.v4 + 1) >> 1);
            output[startIndex + 5] = (byte)((v5 + rs.v5 + 1) >> 1);
            output[startIndex + 6] = (byte)((v6 + rs.v6 + 1) >> 1);
            output[startIndex + 7] = (byte)((v7 + rs.v7 + 1) >> 1);
            output[startIndex + 8] = (byte)((v8 + rs.v8 + 1) >> 1);
            output[startIndex + 9] = (byte)((v9 + rs.v9 + 1) >> 1);
            output[startIndex + 10] = (byte)((v10 + rs.v10 + 1) >> 1);
            output[startIndex + 11] = (byte)((v11 + rs.v11 + 1) >> 1);
            output[startIndex + 12] = (byte)((v12 + rs.v12 + 1) >> 1);
            output[startIndex + 13] = (byte)((v13 + rs.v13 + 1) >> 1);
            output[startIndex + 14] = (byte)((v14 + rs.v14 + 1) >> 1);
            output[startIndex + 15] = (byte)((v15 + rs.v15 + 1) >> 1);
        }
    }

    internal struct BlockCombiner16
    {
        byte[] _plane;
        int _baseIndex;
        bool _hasAverage;
        int _averageIndex;

        const int BLOCK_SIZE = 16;

        public BlockCombiner16(byte[] plane, int sourceIndex)
        {
            _plane = plane;
            _baseIndex = sourceIndex;
            _hasAverage = false;
            _averageIndex = 0;
        }

        public void SetAverage(int sourceIndex)
        {
            _hasAverage = true;
            _averageIndex = sourceIndex;
        }

        public void WriteOut(byte[] destPlane, int destStartIndex, int stride)
        {
            byte[] sourcePlane = _plane;
            int rowSource1Index = _baseIndex;
            int rowDestIndex = destStartIndex;
            if (_hasAverage)
            {
                int rowSource2Index = _averageIndex;
                for (int row = 0; row < BLOCK_SIZE; row++)
                {
                    int source1Index = rowSource1Index;
                    int source2Index = rowSource2Index;
                    int destIndex = rowDestIndex;
                    for (int col = 0; col < BLOCK_SIZE; col++)
                    {
                        destPlane[destIndex] = (byte)(((int)sourcePlane[source1Index] + (int)sourcePlane[source2Index]) >> 1);
                        source1Index ++;
                        source2Index ++;
                        destIndex ++;
                    }
                    rowSource1Index += stride;
                    rowSource2Index += stride;
                    rowDestIndex += stride;

                }
            }
            else
            {
                for (int row = 0; row < BLOCK_SIZE; row++)
                {
                    Array.Copy(sourcePlane, rowSource1Index, destPlane, rowDestIndex, BLOCK_SIZE);
                    rowSource1Index += stride;
                    rowDestIndex += stride;
                }
            }
        }


        public void AverageOut(byte[] destPlane, int destStartIndex, ref BlockCombiner16 otherBlock, int stride)
        {
            byte[] sourcePlane = _plane;
            int rowSource1Index = _baseIndex;

            byte[] otherSourcePlane = otherBlock._plane;
            int rowOtherSource1Index = otherBlock._baseIndex;

            int rowDestIndex = destStartIndex;

            if (_hasAverage)
            {
                int rowSource2Index = _averageIndex;

                if (otherBlock._hasAverage)
                {
                    int rowOtherSource2Index = otherBlock._averageIndex;

                    for (int row = 0; row < BLOCK_SIZE; row++)
                    {
                        int source1Index = rowSource1Index;
                        int source2Index = rowSource2Index;
                        int otherSource1Index = rowOtherSource1Index;
                        int otherSource2Index = rowOtherSource2Index;
                        int destIndex = rowDestIndex;
                        for (int col = 0; col < BLOCK_SIZE; col++)
                        {
                            destPlane[destIndex] = (byte)((
                                (int)sourcePlane[source1Index] +
                                (int)sourcePlane[source2Index] +
                                (int)otherSourcePlane[otherSource1Index] +
                                (int)otherSourcePlane[otherSource2Index]
                                ) >> 2);
                            source1Index++;
                            source2Index++;
                            otherSource1Index++;
                            otherSource2Index++;
                            destIndex++;
                        }
                        rowSource1Index += stride;
                        rowSource2Index += stride;
                        rowOtherSource1Index += stride;
                        rowOtherSource2Index += stride;
                        rowDestIndex += stride;
                    }
                }
                else
                {
                    for (int row = 0; row < BLOCK_SIZE; row++)
                    {
                        int source1Index = rowSource1Index;
                        int source2Index = rowSource2Index;
                        int otherSource1Index = rowOtherSource1Index;
                        int destIndex = rowDestIndex;
                        for (int col = 0; col < BLOCK_SIZE; col++)
                        {
                            destPlane[destIndex] = (byte)((
                                (int)sourcePlane[source1Index] +
                                (int)sourcePlane[source2Index] +
                                (((int)otherSourcePlane[otherSource1Index]) << 1)
                                ) >> 2);
                            source1Index++;
                            source2Index++;
                            otherSource1Index++;
                            destIndex++;
                        }
                        rowSource1Index += stride;
                        rowSource2Index += stride;
                        rowOtherSource1Index += stride;
                        rowDestIndex += stride;
                    }
                }
            }
            else
            {
                if (otherBlock._hasAverage)
                {
                    int rowOtherSource2Index = otherBlock._averageIndex;

                    for (int row = 0; row < BLOCK_SIZE; row++)
                    {
                        int source1Index = rowSource1Index;
                        int otherSource1Index = rowOtherSource1Index;
                        int otherSource2Index = rowOtherSource2Index;
                        int destIndex = rowDestIndex;
                        for (int col = 0; col < BLOCK_SIZE; col++)
                        {
                            destPlane[destIndex] = (byte)((
                                (((int)sourcePlane[source1Index]) << 1) +
                                (int)otherSourcePlane[otherSource1Index] +
                                (int)otherSourcePlane[otherSource2Index]
                                ) >> 2);
                            source1Index++;
                            otherSource1Index++;
                            otherSource2Index++;
                            destIndex++;
                        }
                        rowSource1Index += stride;
                        rowOtherSource1Index += stride;
                        rowOtherSource2Index += stride;
                        rowDestIndex += stride;
                    }
                }
                else
                {
                    for (int row = 0; row < BLOCK_SIZE; row++)
                    {
                        int source1Index = rowSource1Index;
                        int otherSource1Index = rowOtherSource1Index;
                        int destIndex = rowDestIndex;
                        for (int col = 0; col < BLOCK_SIZE; col++)
                        {
                            destPlane[destIndex] = (byte)((
                                (int)sourcePlane[source1Index] +
                                (int)otherSourcePlane[otherSource1Index]
                                ) >> 1);

                            source1Index++;
                            otherSource1Index++;
                            destIndex++;
                        }
                        rowSource1Index += stride;
                        rowOtherSource1Index += stride;
                        rowDestIndex += stride;
                    }
                }
            }
        }
    }

    internal struct BlockCombiner8
    {
        byte[] _plane;
        int _baseIndex;
        bool _hasAverage;
        int _averageIndex;

        const int BLOCK_SIZE = 8;

        public BlockCombiner8(byte[] plane, int sourceIndex)
        {
            _plane = plane;
            _baseIndex = sourceIndex;
            _hasAverage = false;
            _averageIndex = 0;
        }

        public void SetAverage(int sourceIndex)
        {
            _hasAverage = true;
            _averageIndex = sourceIndex;
        }

        public void WriteOut(byte[] destPlane, int destStartIndex, int stride)
        {
            byte[] sourcePlane = _plane;
            int rowSource1Index = _baseIndex;
            int rowDestIndex = destStartIndex;
            if (_hasAverage)
            {
                int rowSource2Index = _averageIndex;
                for (int row = 0; row < BLOCK_SIZE; row++)
                {
                    int source1Index = rowSource1Index;
                    int source2Index = rowSource2Index;
                    int destIndex = rowDestIndex;
                    for (int col = 0; col < BLOCK_SIZE; col++)
                    {
                        destPlane[destIndex] = (byte)(((int)sourcePlane[source1Index] + (int)sourcePlane[source2Index]) >> 1);
                        source1Index++;
                        source2Index++;
                        destIndex++;
                    }
                    rowSource1Index += stride;
                    rowSource2Index += stride;
                    rowDestIndex += stride;

                }
            }
            else
            {
                for (int row = 0; row < BLOCK_SIZE; row++)
                {
                    Array.Copy(sourcePlane, rowSource1Index, destPlane, rowDestIndex, BLOCK_SIZE);
                    rowSource1Index += stride;
                    rowDestIndex += stride;
                }
            }
        }


        public void AverageOut(byte[] destPlane, int destStartIndex, ref BlockCombiner8 otherBlock, int stride)
        {
            byte[] sourcePlane = _plane;
            int rowSource1Index = _baseIndex;

            byte[] otherSourcePlane = otherBlock._plane;
            int rowOtherSource1Index = otherBlock._baseIndex;

            int rowDestIndex = destStartIndex;

            if (_hasAverage)
            {
                int rowSource2Index = _averageIndex;

                if (otherBlock._hasAverage)
                {
                    int rowOtherSource2Index = otherBlock._averageIndex;

                    for (int row = 0; row < BLOCK_SIZE; row++)
                    {
                        int source1Index = rowSource1Index;
                        int source2Index = rowSource2Index;
                        int otherSource1Index = rowOtherSource1Index;
                        int otherSource2Index = rowOtherSource2Index;
                        int destIndex = rowDestIndex;
                        for (int col = 0; col < BLOCK_SIZE; col++)
                        {
                            destPlane[destIndex] = (byte)((
                                (int)sourcePlane[source1Index] +
                                (int)sourcePlane[source2Index] +
                                (int)otherSourcePlane[otherSource1Index] +
                                (int)otherSourcePlane[otherSource2Index]
                                ) >> 2);
                            source1Index++;
                            source2Index++;
                            otherSource1Index++;
                            otherSource2Index++;
                            destIndex++;
                        }
                        rowSource1Index += stride;
                        rowSource2Index += stride;
                        rowOtherSource1Index += stride;
                        rowOtherSource2Index += stride;
                        rowDestIndex += stride;
                    }
                }
                else
                {
                    for (int row = 0; row < BLOCK_SIZE; row++)
                    {
                        int source1Index = rowSource1Index;
                        int source2Index = rowSource2Index;
                        int otherSource1Index = rowOtherSource1Index;
                        int destIndex = rowDestIndex;
                        for (int col = 0; col < BLOCK_SIZE; col++)
                        {
                            destPlane[destIndex] = (byte)((
                                (int)sourcePlane[source1Index] +
                                (int)sourcePlane[source2Index] +
                                (((int)otherSourcePlane[otherSource1Index]) << 1)
                                ) >> 2);
                            source1Index++;
                            source2Index++;
                            otherSource1Index++;
                            destIndex++;
                        }
                        rowSource1Index += stride;
                        rowSource2Index += stride;
                        rowOtherSource1Index += stride;
                        rowDestIndex += stride;
                    }
                }
            }
            else
            {
                if (otherBlock._hasAverage)
                {
                    int rowOtherSource2Index = otherBlock._averageIndex;

                    for (int row = 0; row < BLOCK_SIZE; row++)
                    {
                        int source1Index = rowSource1Index;
                        int otherSource1Index = rowOtherSource1Index;
                        int otherSource2Index = rowOtherSource2Index;
                        int destIndex = rowDestIndex;
                        for (int col = 0; col < BLOCK_SIZE; col++)
                        {
                            destPlane[destIndex] = (byte)((
                                (((int)sourcePlane[source1Index]) << 1) +
                                (int)otherSourcePlane[otherSource1Index] +
                                (int)otherSourcePlane[otherSource2Index]
                                ) >> 2);
                            source1Index++;
                            otherSource1Index++;
                            otherSource2Index++;
                            destIndex++;
                        }
                        rowSource1Index += stride;
                        rowOtherSource1Index += stride;
                        rowOtherSource2Index += stride;
                        rowDestIndex += stride;
                    }
                }
                else
                {
                    for (int row = 0; row < BLOCK_SIZE; row++)
                    {
                        int source1Index = rowSource1Index;
                        int otherSource1Index = rowOtherSource1Index;
                        int destIndex = rowDestIndex;
                        for (int col = 0; col < BLOCK_SIZE; col++)
                        {
                            destPlane[destIndex] = (byte)((
                                (int)sourcePlane[source1Index] +
                                (int)otherSourcePlane[otherSource1Index]
                                ) >> 1);

                            source1Index++;
                            otherSource1Index++;
                            destIndex++;
                        }
                        rowSource1Index += stride;
                        rowOtherSource1Index += stride;
                        rowDestIndex += stride;
                    }
                }
            }
        }
    }


    internal struct IntBlock16
    {
        public IntRow16 r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15;


        public IntBlock16(IntRow16 r0, IntRow16 r1, IntRow16 r2, IntRow16 r3, IntRow16 r4, IntRow16 r5, IntRow16 r6, IntRow16 r7,
            IntRow16 r8, IntRow16 r9, IntRow16 r10, IntRow16 r11, IntRow16 r12, IntRow16 r13, IntRow16 r14, IntRow16 r15)
        {
            this.r0 = r0;
            this.r1 = r1;
            this.r2 = r2;
            this.r3 = r3;
            this.r4 = r4;
            this.r5 = r5;
            this.r6 = r6;
            this.r7 = r7;
            this.r8 = r8;
            this.r9 = r9;
            this.r10 = r10;
            this.r11 = r11;
            this.r12 = r12;
            this.r13 = r13;
            this.r14 = r14;
            this.r15 = r15;
        }

        public IntBlock16(int singleVal)
        {
            r0 =
            r1 =
            r2 =
            r3 =
            r4 =
            r5 =
            r6 =
            r7 =
            r8 =
            r9 =
            r10 =
            r11 =
            r12 =
            r13 =
            r14 =
            r15 =
            new IntRow16(singleVal);
        }

        public IntBlock16(IntRow16 singleVal)
        {
            r0 =
            r1 =
            r2 =
            r3 =
            r4 =
            r5 =
            r6 =
            r7 =
            r8 =
            r9 =
            r10 =
            r11 =
            r12 =
            r13 =
            r14 =
            r15 = singleVal;
        }

        public IntBlock16(byte[] numbers, int startIndex, int stride)
        {
            r0 = new IntRow16(numbers, startIndex); startIndex += stride;
            r1 = new IntRow16(numbers, startIndex); startIndex += stride;
            r2 = new IntRow16(numbers, startIndex); startIndex += stride;
            r3 = new IntRow16(numbers, startIndex); startIndex += stride;
            r4 = new IntRow16(numbers, startIndex); startIndex += stride;
            r5 = new IntRow16(numbers, startIndex); startIndex += stride;
            r6 = new IntRow16(numbers, startIndex); startIndex += stride;
            r7 = new IntRow16(numbers, startIndex); startIndex += stride;
            r8 = new IntRow16(numbers, startIndex); startIndex += stride;
            r9 = new IntRow16(numbers, startIndex); startIndex += stride;
            r10 = new IntRow16(numbers, startIndex); startIndex += stride;
            r11 = new IntRow16(numbers, startIndex); startIndex += stride;
            r12 = new IntRow16(numbers, startIndex); startIndex += stride;
            r13 = new IntRow16(numbers, startIndex); startIndex += stride;
            r14 = new IntRow16(numbers, startIndex); startIndex += stride;
            r15 = new IntRow16(numbers, startIndex);
        }


        public void ClampAndWrite(byte[] output, int startIndex, int stride)
        {
            r0.ClampAndWrite(output, startIndex + stride * 0);
            r1.ClampAndWrite(output, startIndex + stride * 1);
            r2.ClampAndWrite(output, startIndex + stride * 2);
            r3.ClampAndWrite(output, startIndex + stride * 3);
            r4.ClampAndWrite(output, startIndex + stride * 4);
            r5.ClampAndWrite(output, startIndex + stride * 5);
            r6.ClampAndWrite(output, startIndex + stride * 6);
            r7.ClampAndWrite(output, startIndex + stride * 7);
            r8.ClampAndWrite(output, startIndex + stride * 8);
            r9.ClampAndWrite(output, startIndex + stride * 9);
            r10.ClampAndWrite(output, startIndex + stride * 10);
            r11.ClampAndWrite(output, startIndex + stride * 11);
            r12.ClampAndWrite(output, startIndex + stride * 12);
            r13.ClampAndWrite(output, startIndex + stride * 13);
            r14.ClampAndWrite(output, startIndex + stride * 14);
            r15.ClampAndWrite(output, startIndex + stride * 15);
        }


        public void AverageWrite(ref IntBlock16 rs, byte[] output, int startIndex, int stride)
        {
            r0.AverageWrite(ref rs.r0, output, startIndex); startIndex += stride;
            r1.AverageWrite(ref rs.r1, output, startIndex); startIndex += stride;
            r2.AverageWrite(ref rs.r2, output, startIndex); startIndex += stride;
            r3.AverageWrite(ref rs.r3, output, startIndex); startIndex += stride;
            r4.AverageWrite(ref rs.r4, output, startIndex); startIndex += stride;
            r5.AverageWrite(ref rs.r5, output, startIndex); startIndex += stride;
            r6.AverageWrite(ref rs.r6, output, startIndex); startIndex += stride;
            r7.AverageWrite(ref rs.r7, output, startIndex); startIndex += stride;
            r8.AverageWrite(ref rs.r8, output, startIndex); startIndex += stride;
            r9.AverageWrite(ref rs.r9, output, startIndex); startIndex += stride;
            r10.AverageWrite(ref rs.r10, output, startIndex); startIndex += stride;
            r11.AverageWrite(ref rs.r11, output, startIndex); startIndex += stride;
            r12.AverageWrite(ref rs.r12, output, startIndex); startIndex += stride;
            r13.AverageWrite(ref rs.r13, output, startIndex); startIndex += stride;
            r14.AverageWrite(ref rs.r14, output, startIndex); startIndex += stride;
            r15.AverageWrite(ref rs.r15, output, startIndex);
        }

        public void SimpleWrite(byte[] output, int startIndex, int stride)
        {
            r0.SimpleWrite(output, startIndex + stride * 0);
            r1.SimpleWrite(output, startIndex + stride * 1);
            r2.SimpleWrite(output, startIndex + stride * 2);
            r3.SimpleWrite(output, startIndex + stride * 3);
            r4.SimpleWrite(output, startIndex + stride * 4);
            r5.SimpleWrite(output, startIndex + stride * 5);
            r6.SimpleWrite(output, startIndex + stride * 6);
            r7.SimpleWrite(output, startIndex + stride * 7);
            r8.SimpleWrite(output, startIndex + stride * 8);
            r9.SimpleWrite(output, startIndex + stride * 9);
            r10.SimpleWrite(output, startIndex + stride * 10);
            r11.SimpleWrite(output, startIndex + stride * 11);
            r12.SimpleWrite(output, startIndex + stride * 12);
            r13.SimpleWrite(output, startIndex + stride * 13);
            r14.SimpleWrite(output, startIndex + stride * 14);
            r15.SimpleWrite(output, startIndex + stride * 15);
        }

        public IntBlock16 Add(IntBlock16 rs)
        {
            return new IntBlock16(
                r0.Add(rs.r0),
                r1.Add(rs.r1),
                r2.Add(rs.r2),
                r3.Add(rs.r3),
                r4.Add(rs.r4),
                r5.Add(rs.r5),
                r6.Add(rs.r6),
                r7.Add(rs.r7),
                r8.Add(rs.r8),
                r9.Add(rs.r9),
                r10.Add(rs.r10),
                r11.Add(rs.r11),
                r12.Add(rs.r12),
                r13.Add(rs.r13),
                r14.Add(rs.r14),
                r15.Add(rs.r15)
                );
        }

        public IntBlock16 RightShift(int rs)
        {
            return new IntBlock16(
                    r0.RightShift(rs),
                    r1.RightShift(rs),
                    r2.RightShift(rs),
                    r3.RightShift(rs),
                    r4.RightShift(rs),
                    r5.RightShift(rs),
                    r6.RightShift(rs),
                    r7.RightShift(rs),
                    r8.RightShift(rs),
                    r9.RightShift(rs),
                    r10.RightShift(rs),
                    r11.RightShift(rs),
                    r12.RightShift(rs),
                    r13.RightShift(rs),
                    r14.RightShift(rs),
                    r15.RightShift(rs)
                );
        }
    }
}
